/***********************************************************************************
**  
* @copyright (c) 2010-2019, ChengDu Duyer Technology Co., LTD. All Right Reserved.
*
************************************************************************************/
/**
* @file	    mjs_server.cpp
* @version     
* @brief      
* @author   duye
* @date	    2016-12-22
* @note 
*
* 1. 2016-12-22 duye Created this file
*/

#include <duye_type.h>
#include <duye_cfg_mgr.h>
#include <duye_timer.h>
#include <mj_net_protocol.h>
#include <mjs_client_proxy.h>
#include <mjs_mysql.h>

// begin action include
#include <mjs_auth_action.h>
#include <mjs_web_auth_action.h>
#include <mjs_hb_action.h>
#include <mjs_sysver_action.h>
// end action include

#include <mjs_client.h>
#include <mjs_web_client.h>
#include <mjs_server.h>

static const int8* DUYE_LOG_PREFIX = "mangjings.server";

MJServer::MJServer() {}

MJServer::~MJServer() {
	duye::AutoLock lock(m_client_map_mutex);
	ClientMap::iterator iter = m_client_map.begin();
	for (; iter != m_client_map.end(); ++iter) {
		m_tcp_server.removeClient(iter->first);
		delete iter->second;
	}
	m_client_map.clear();

	MJSOnActionMap::iterator act_iter = m_on_action_map.begin();
	for (; act_iter != m_on_action_map.end(); ++act_iter) {
		delete act_iter->second;
	}
	m_on_action_map.clear();
}

bool MJServer::startup() {
	if (!connectDB()) return false;

	// read message protocol config
	std::string msg_conf_path;
	if (!duye::CfgMgr::ins().getValue("node:global.param:msg_conf_path", msg_conf_path, "value")) {
		DUYE_ERROR("get 'node:global.param:msg_conf_path' failed");
		return false;
	}

	if (msg_conf_path.empty()) {
		DUYE_ERROR("msg_conf_path is empty");
		return false;
	}

	if (!MJMsgFactory::ins().init(msg_conf_path, MJMsgFactory::MJ_PEER_SERVER)) {
		DUYE_ERROR("init MJMsgFactory faield");
		return false;
	}

	// startup timerserver
	if (!duye::TimerServer::instance().startServer()) {
		DUYE_ERROR("startup timer service faield");
		return false;
	}

	if (!duye::CfgMgr::ins().getValue("node:mjw.param:ip", m_web_ip, "value")) {
		DUYE_ERROR("get mjw config ip failed");
		return false;
	}

	initOnAction();

	setClientConnStatusToOffline();

	if (!createServer()) {
		DUYE_ERROR("createServer failed");
		return false;
	}

	return true;
}

bool MJServer::stop(const bool block) {
	setClientConnStatusToOffline();
	return m_tcp_server.stopServer();
}

bool MJServer::onNewcon(const duye::IPv4Addr& clientAddr, const int32 clientSockfd) {
	DUYE_DEBUG("accept new connect address=%s:%u", clientAddr.ipStr(), clientAddr.port());
	m_sockfdAndIpMap.erase(clientSockfd);
	m_sockfdAndIpMap.insert(std::make_pair(clientSockfd, (int8*)clientAddr.ipStr()));
	m_tcp_server.addClient(clientSockfd);
	return true;
}

bool MJServer::onDiscon(const int32 clientSockfd) {
	DUYE_DEBUG("disconnect %s", getClientIP(clientSockfd).c_str());
	removeClient(clientSockfd);
	return true;
}

bool MJServer::onRecved(const int32 clientSockfd, const int8* data, const uint64 len) {
	std::string ip = getClientIP(clientSockfd);

	DUYE_DEBUG("received data(%d) from %s >>> \n%s", len, ip.c_str(), data);

	SockfdAndIpMap::iterator ip_iter = m_sockfdAndIpMap.find(clientSockfd);
	if (ip_iter == m_sockfdAndIpMap.end()) {
		DUYE_ERROR("don't found ip with fd:%d from m_sockfdAndIpMap, but received data", clientSockfd);
		return false;
	}	

	std::list<MJMsgAction> action_list;
	if (!MJMsgFactory::ins().fromatMsgXml(data, len, action_list)) {
		DUYE_ERROR("parse action failed");
		return false;
	}

	if (action_list.empty()) {
		DUYE_ERROR("action_list is empty");
		return false;
	}

	duye::AutoLock lock(m_client_map_mutex);
	ClientMap::iterator iter = m_client_map.find(clientSockfd);
	if (iter == m_client_map.end()) {
		MJMsgAction& action = action_list.front();
		if (action.getName() == "auth") {
			addMangjingc(clientSockfd, ip_iter->second);
		} else if (action.getName() == "web_auth") {
			addMangjingw(clientSockfd, ip_iter->second);
		} else {
			DUYE_ERROR("client %s not exist", ip_iter->second.c_str());
			return false;
		}

		iter = m_client_map.find(clientSockfd);
		if (iter == m_client_map.end()) {
			DUYE_ERROR("test client %s not exist", ip_iter->second.c_str());
			return false;
		}
	}

	return iter->second->handleAction(action_list);
}

bool MJServer::connectDB() {
	// connect mysql
	std::string mysql_addr;
	uint16 mysql_port = 0;
	std::string mysql_user;
	std::string mysql_pwd;
	std::string mysql_database;
	duye::CfgMgr::ins().getValue("node:mysql.param:addr", mysql_addr, "value");
	duye::CfgMgr::ins().getValue("node:mysql.param:port", mysql_port, "value");
	duye::CfgMgr::ins().getValue("node:mysql.param:user", mysql_user, "value");
	duye::CfgMgr::ins().getValue("node:mysql.param:pwd", mysql_pwd, "value");
	duye::CfgMgr::ins().getValue("node:mysql.param:database", mysql_database, "value");

	bool mysql_flage = true;
	if (mysql_addr.empty()) {
		DUYE_ERROR("mysql addr is empty");
		mysql_flage = false;
	}

	if (mysql_user.empty()) {
		DUYE_ERROR("mysql user is empty");
		mysql_flage = false;
	}

	if (mysql_pwd.empty()) {
		DUYE_ERROR("mysql password is empty");
		mysql_flage = false;
	}

	if (mysql_database.empty()) {
		DUYE_ERROR("mysql database is empty");
		mysql_flage = false;
	}	

	if (!mysql_flage) {
		return false;
	}

	if (!MJSMysql::ins().init(mysql_addr, mysql_port, mysql_user, mysql_pwd, mysql_database)) {
		DUYE_ERROR("init mysql [FAILED]");
		return false;
	}

	return true;
}

void MJServer::initOnAction() {
	// handle mangjings and mangjingc event
	m_on_action_map.insert(std::make_pair("auth", new MJSAuthAction()));
	m_on_action_map.insert(std::make_pair("heartbeat", new MJSHBAction()));
	m_on_action_map.insert(std::make_pair("sysver", new MJSSysVerAction()));

	// handle mangjings and mangjingw event
	m_on_action_map.insert(std::make_pair("web_auth", new MJSWebAuthAction()));
}

bool MJServer::createServer() {
	std::string server_ip;
	if (!duye::CfgMgr::ins().getValue("node:server.param:ip", server_ip, "value")) {
		DUYE_ERROR("get server ip from config failed");
		return false;
	}

	std::string interface_name;
	if (server_ip.empty()) {
		DUYE_WARN("get server ip is empty from config, will get interface");

		// get interface 
		if (!duye::CfgMgr::ins().getValue("node:server.param:interface", interface_name, "value")) {
			DUYE_ERROR("get interface from config failed");
			return false;
		}

		if (interface_name.empty()) {
			DUYE_ERROR("get interface is empty from config");
			return false;
		}
	}

	uint16 server_port = 0;
	if (!duye::CfgMgr::ins().getValue("node:server.param:port", server_port, "value")) {
		DUYE_ERROR("get server port from config failed");
		return false;
	}

	if (server_port == 0) {
		DUYE_ERROR("get server port = 0");
		return false;			
	}

	uint16 max_con = 0;
	if (!duye::CfgMgr::ins().getValue("node:server.param:max", max_con, "value")) {
		DUYE_ERROR("get server port from config failed");
		return false;
	}

	if (max_con == 0) {
		DUYE_ERROR("get server max_con = 0");
		return false;
	}	

	duye::NetServerPara server_para;
	if (!server_ip.empty()) {
		server_para.bindAddr = server_ip;
	} else {
		server_para.localDevName = interface_name;
	}
	server_para.port = server_port;
	server_para.maxConn = max_con;
	
	if (!m_tcp_server.initServer(server_para, this)) {
		DUYE_ERROR("m_tcp_server.initServer() failed");
		return false;
	}
	
	return m_tcp_server.startServer();
}

bool MJServer::setClientConnStatusToOffline() {
	std::string sql = "UPDATE server_info SET con_status = ?";
	int32 rows = MJSMysql::ins().update(sql, MJSMysql::Params("offline"));
	if (rows < 0) {
		DUYE_WARN("setting all client con_status to offline [FAILED]");
		return false;
	} else {
		DUYE_INFO("setting all client con_status to offline [OK]");
		return true;
	}
}

void MJServer::addMangjingc(const int32 fd, const std::string& ip) {
	duye::AutoLock lock(m_client_map_mutex);
	ClientMap::iterator iter = m_client_map.find(fd);
	if (iter != m_client_map.end()) {
		m_client_map.erase(iter);
	}

	m_client_map.insert(std::make_pair(fd, new MJSClientProxy(fd, ip, &m_on_action_map, this)));
}

void MJServer::addMangjingw(const int32 fd, const std::string& ip) {
	if (m_web_ip.compare(ip) == 0) {
		duye::AutoLock lock(m_client_map_mutex);
		ClientMap::iterator iter = m_client_map.find(fd);
		if (iter != m_client_map.end()) {
			m_client_map.erase(iter);
		}

		m_client_map.insert(std::make_pair(fd, new MJSWebClient(fd, ip, &m_on_action_map, this)));
	} else {
		DUYE_ERROR("mangjingw auth ip(%s) unmatch to conifg ip(%s)", ip.c_str(), m_web_ip.c_str());
	}
}

void MJServer::removeClient(const int32 fd) {
	duye::AutoLock lock(m_client_map_mutex);
	m_client_map.erase(fd);
	// ClientMap::iterator iter = m_client_map.find(fd);
	// if (iter == m_client_map.end()) {
	// 	return;
	// }

	// if (iter->second->connStatus() == MJSClientProxy::ON_LINE) {
	// 	m_tcp_server.removeClient(fd);
	// 	iter->second->setConnStatus(MJSClientProxy::OFF_LINE);
	// 	iter->second->setAuthStatus(MJSClientProxy::AUTH_NO);
	// }
}

std::string MJServer::getClientIP(const int32 fd) {
	ClientMap::iterator iter = m_client_map.find(fd);
	if (iter == m_client_map.end()) {
		return "";
	}

	return iter->second->getIP();
}

int64 MJServer::sendToClient(const int32 sockfd, const int8* data, const uint64 len) {
	return m_tcp_server.send(sockfd, data, len);
}